package com.innovate.nms.service.channel;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.stereotype.Component;

import com.google.common.base.Strings;
import com.google.common.eventbus.Subscribe;
import com.innovate.nms.domain.UdpConfig;
import com.ivan.toolkit.common.Events;

import lombok.extern.log4j.Log4j;

@Component
@Log4j
public class UdpChannel extends Channel implements Runnable {
	

	private static final int defaultPort = 7373;
	
	Thread thread = null;
	
	DatagramSocket udpSocket = null;
	
	int remotePort = defaultPort;
	
	public UdpChannel() {
		super("UDP");
	}

	@PostConstruct
	public void start() throws Exception {	
		
		Events.register(this);
		
		thread = new Thread(this);
		thread.setName("UDP PROVIDER");
		thread.setDaemon(true);
		thread.start();
	}

	@PreDestroy
	public void stop() throws Exception {
	}
	
	@Subscribe
	public void onEvent(Events event) {
		
		if(event.getKey() instanceof UdpConfig) {
			log.info("Config changed, try close channel and restart later");
			try {
				if(udpSocket != null) {
					udpSocket.close();
				}
			} catch (Exception e) {
			} finally {
				udpSocket = null;
			}
		}
	}

	@Override
	public int getTimeout() {
		UdpConfig config = UdpConfig.instance.get();
		return config.getTimeout();
	}

	@Override
	public void run() {
		
		while(true) {
			
			try {
			
				// create udp socket
				UdpConfig config = UdpConfig.instance.get();
				
				int localPort = config.getListenPort();
				
				try {
					udpSocket = new DatagramSocket(localPort);
					udpSocket.setSoTimeout(2000);
				} catch (SocketException e) {
					log.error("Create UDP socket failed, port is " + localPort, e);
				}
				
				if(udpSocket == null) { 
					Thread.sleep(5000);
					continue;
				}
				
				
				// prepare receive packet
				byte[] buff = new byte[512];
				DatagramPacket packet = new DatagramPacket(buff, 0, 512);
				
				// loop for receive data
				while(true) {
					
					try {
						udpSocket.receive(packet);
						
						processPcket(packet);
						
					} catch(SocketTimeoutException e) {
						continue;
					} catch (IOException e) {
						log.info("IOException happen, maybe config changed.");
						break;
					}
					
				}
			
			} catch(InterruptedException e) {
				
				return;
				
			} finally {
				
				try {
					if(udpSocket != null) {
						udpSocket.close();
					}
				} catch (Exception e) {
				} finally {
					udpSocket = null;
				}
			}
		
		}
		
	}
	
	void processPcket(DatagramPacket packet) {
		InetAddress ia = packet.getAddress();
		byte[] data = packet.getData();
		int len = packet.getLength();
		
		String address = ia.getHostAddress();
		String content = new String(data, 0, len);
		ChannelEvent pdu = new ChannelEvent(this, address, content);
		
		log.info("NET RESPONSE: source=" + address + " content=" + content);
		
		eventbus.post(pdu);
	}
	
	@Override
	public synchronized void send(String target, String content) throws Exception {
		
		if(udpSocket == null) 
			throw new RuntimeException("UDP套接字未准备好！");
		
		if(Strings.isNullOrEmpty(target)) 
			throw new RuntimeException("目标未指定！");
		
		if(Strings.isNullOrEmpty(content)) 
			throw new RuntimeException("内容未指定！");
		
		log.info("NET REQUEST: target=" + target + " content=" + content);
		
		{
			
			try {

				InetAddress ia = Inet4Address.getByName(target);
				byte[] data = content.getBytes();
				
				DatagramPacket packet = 
						new DatagramPacket(data, 0, data.length);
				packet.setAddress(ia);
				packet.setPort(remotePort);
				
				/*
				DatagramSocket udp = new DatagramSocket();
				udp.send(packet);
				udp.close();
				*/
				udpSocket.send(packet);
				
			} catch (UnknownHostException e) {
				//e.printStackTrace();
				throw new RuntimeException("未知的目标主机！", e);
			} catch (IOException e) {
				//e.printStackTrace();
				throw new RuntimeException("UDP套接字IO错误！", e);
			}
		}
	}

	@Override
	public boolean isReady() {
		return udpSocket != null && !udpSocket.isClosed();
	}

}
